Android最简单的热修复原理解析

您所在的位置:网站首页 android hook classloader Android最简单的热修复原理解析

Android最简单的热修复原理解析

#Android最简单的热修复原理解析| 来源: 网络整理| 查看: 265

接上一篇Android中的ClassLoader分析,实现一个最简单的热修复的例子,意在解释原理。 上一篇我们明白了Android加载类的原理,这一篇我们来进行实践。

我们回顾一下Android ClassLoader的原理并理一下思路:

Android使用了PathClassLoader进行类的加载,实际的加载类是它的父类BaseDexClassLoader的DexPathList来进行加载的,load进来的dex会放在它的成员变量ArrayList[DexFile] elements里面,需要加载的时候按顺序依次查找并加载。 系统给我们提供了一个叫做DexClassLoader的类来加载非系统的Dex,我们可以用这个类来加载我们自己的dex文件,只需要给它指定一个可以访问到的dex文件路径。

So,我们要做的就是把我们需要替换的class打成dex或者包含classes.dex的jar或者zip(Android最终访问的依旧是classes.dex文件), 并且把DexClassLoader的DexPathList的elements取出来放到PathClassLoader的DexPathList的elements的最前面。

实现一个基本的Android Demo //MainActivity.java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = findViewById(R.id.sample_text); tv.setText(Util.getShow()); } //Util.java public class Util { public static String getShow() { return "Mr Wrong!"; } } 复制代码

代码很简单,Util.getShow()返回一个字符串,并显示在UI上,所以它应该是这么显示的。

把需要修改的class打成dex

我们就把Util.getShow()方法修改一个返回值

public class Util { public static String getShow() { return "Mr Right"; } } 复制代码

然后我们需要把这个类打到dex里面。 我们可以run一下之后去这个目录下面找我们的Util.class

testHotFix/app/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/hook/yocn/testhotfix/Util.class 复制代码

如果找不到我们也可以自己用javac命令生成一个

javac Util.java //会在本目录生成一个Util.class文件 复制代码

然后我们需要把这个Util.class生成dex

dx --dex --output Util.dex Util.class 复制代码

dx命令在Android SDK下,我本地目录为/Users/yocn/Library/Android/sdk/build-tools/28.0.3,方便起见,可以把这个目录配置到环境变量里面。

现在我们的目录结构是这样的,如果在testhotfix目录下执行上面生成dex的方法,我这里是会报错: MacBook:testhotfix yocn$ dx --dex --output Util.dex Util.class PARSE ERROR: class name (com/hook/yocn/testhotfix/Util) does not match path (Util.class) ...while parsing Util.class 1 error; aborting 复制代码

所以我来到java目录下执行

dx --dex --output Util.dex com/hook/yocn/testhotfix/Util.class 复制代码

发现main/java目录下生成了一个Util.dex文件。

我们把这个dex文件放到SD卡根目录下。 adb push Util.dex /mnt/sdcard/ 复制代码

然后编写代码:

public class MainActivity extends Activity { private static String TAG = "yocnAA"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = findViewById(R.id.sample_text); try { init(); } catch (IllegalAccessException | NoSuchFieldException | ClassNotFoundException e) { e.printStackTrace(); } tv.setText(Util.getShow()); } private void init() throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException { ClassLoader pathClassLoader = getClassLoader(); //SD卡根目录 String dexPath = Environment.getExternalStorageDirectory().getAbsolutePath(); //存放加载进来的dex的目录 String optimizedDirectory = getFilesDir().getAbsolutePath() + File.separator + "odex"; //dexPath:dex或者包含dex的jar/apk文件路径,多个需要用File.pathSeparator分隔开 //optimizedDirectory:odex的被存放的路径,可以为null,这里就能看出来区别,PathClassLoader设置的是null,DexClassLoader设置的是非null。 //libraryPath:本地库的文件路径,多个需要用File.pathSeparator分隔开 //parent:父classloader DexClassLoader dexClassLoader = new DexClassLoader(dexPath + "/Util.dex", optimizedDirectory, null, pathClassLoader); //得到DexClassLoader的DexPathList,加载dex到Element数组里面 /data/user/0/com.yocn.testhotfix/files Object dexDexPathList = getPathList(dexClassLoader); Object newElementArray = getDexElements(dexDexPathList); //获取PathClassLoader加载的dex Object pathDexPathList = getPathList(pathClassLoader); Object oldElementArray = getDexElements(pathDexPathList); //把DexClassLoader的element[]放到PathClassLoader的element[]里面并且是头部,加载的时候优先加载 Object resultElementArray = combineArray(newElementArray, oldElementArray); //重新设置进去 setField(pathDexPathList, pathDexPathList.getClass(), "dexElements", resultElementArray); } /** * 反射给对象中的属性重新赋值 */ private static void setField(Object obj, Class cl, String field, Object value) throws NoSuchFieldException, IllegalAccessException { Field declaredField = cl.getDeclaredField(field); declaredField.setAccessible(true); declaredField.set(obj, value); } /** * 反射得到对象中的属性值 */ private static Object getField(Object obj, Class cl, String field) throws NoSuchFieldException, IllegalAccessException { Field localField = cl.getDeclaredField(field); localField.setAccessible(true); return localField.get(obj); } /** * 反射得到类加载器中的pathList对象 */ private static Object getPathList(Object baseDexClassLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList"); } /** * 反射得到pathList中的dexElements */ private static Object getDexElements(Object pathList) throws NoSuchFieldException, IllegalAccessException { return getField(pathList, pathList.getClass(), "dexElements"); } /** * 数组合并 */ private static Object combineArray(Object arrayLhs, Object arrayRhs) { Class componentType = arrayLhs.getClass().getComponentType(); // 得到左数组长度(补丁数组) int i = Array.getLength(arrayLhs); // 得到原dex数组长度 int j = Array.getLength(arrayRhs); // 得到总数组长度(补丁数组+原dex数组) int k = i + j; // 创建一个类型为componentType,长度为k的新数组 Object result = Array.newInstance(componentType, k); System.arraycopy(arrayLhs, 0, result, 0, i); System.arraycopy(arrayRhs, 0, result, i, j); return result; } } 复制代码

然后我们把App跑起来,如果看到下面的图说明成功了。

我们还可以把SD卡目录下的Util.dex文件改个名字或者删掉,再重新运行app,看是不是又变回原来的Mr Wrong!了。

参考: 热修复——深入浅出原理与实现



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3